import asyncio
from random import randint
import websockets
import json
import os
import sys
import base64
from time import strftime, time
from random import randint
sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))#把本文件的父目录定位为上级目录的父目录(/Chatlife/Chat => /Chatlife)
from pylogger import pylogger as log
log.init()

# {username(str):{password(str),identify(int),id(int),status(int),?ban_time(int)}, ...}
try:
    with open('./users.json', 'r') as f:
        ALL_USERS: dict = json.load(f)
except:
    ALL_USERS: dict = {} #type:ignore [no-redef]
    with open('./users.json','w') as f:
        json.dump(ALL_USERS,f)
# {user_ID(int):ws_instance(object), ...}
USERS: dict = {}

# {id(str):{friend_id(str):relationship(str),...},...}
try:
    with open('./Chat/friend-net.json', 'r') as f:
        FRIEND_NET:dict = json.load(f)
except:
    FRIEND_NET:dict = {} #type:ignore [no-redef]
    with open('./Chat/friend-net.json','w') as f:
        json.dump(FRIEND_NET,f)
try:
    with open('./server-paths.json', 'r') as f:
        NET: dict = json.load(f)
except:
    raise BaseException
# _HISTORY: {msgID:[time,username,message,id],...],...}
# _TIMELINE: [msgID,...]
try:
    with open('./Chat/history.json', 'r') as f:
        __HISTORY = json.load(f)
        _HISTORY: dict = __HISTORY[0]
        _TIMELINE: list = __HISTORY[1]
except:
    _HISTORY:dict = {} #type:ignore [no-redef]
    _TIMELINE:list = [] #type:ignore [no-redef]
    with open('./Chat/history.json','w') as f:
        json.dump([_HISTORY,_TIMELINE],f)
while len(_HISTORY) > 200:
    del _HISTORY[_TIMELINE.pop(0)]


existMsgID = [id for id in _HISTORY]
existUserID = [user["id"] for user in ALL_USERS.values()]
HEADS = os.listdir('./Chat/static/images/head/')
USER_writing = False
HISTORY_writing = False
FRIEND_NET_writing = False
socketIDs : dict[object,str] = {}

def to_json(val):
    log.info("发送:"+str(val))
    val = json.encoder.JSONEncoder().encode(val)
    return val


def updateHistory():
    global _HISTORY, _TIMELINE, HISTORY_writing
    while HISTORY_writing:
        pass
    HISTORY_writing = True
    with open("./Chat/history.json", 'w') as f:
        json.dump([_HISTORY, _TIMELINE], f)
    HISTORY_writing = False


def HISTORY():
    return _HISTORY.copy()


def TIMELINE():
    return _TIMELINE.copy()


def createMsgID() -> str:
    global existMsgID
    msgID = str(randint(0, 100000))
    while msgID in existMsgID:
        msgID = str(randint(0, 100000))
    existMsgID.append(msgID)
    return msgID

def createSocketID(username) -> str:
    global socketIDs
    new_socketID = ""
    for _ in range(16):
        choose = randint(1,3)
        if choose == 1:
            new_socketID += chr(randint(48,57))
        elif choose == 2:
            new_socketID += chr(randint(65,90))
        elif choose == 3:
            new_socketID += chr(randint(97,122))
    while new_socketID in socketIDs.values():
        for _ in range(16):
            choose = randint(1,3)
            if choose == 1:
                new_socketID += chr(randint(48,57))
            elif choose == 2:
                new_socketID += chr(randint(65,90))
            elif choose == 3:
                new_socketID += chr(randint(97,122))
    socketIDs[username] = new_socketID
    return new_socketID
    
    

def createUserID():
    global existUserID
    ID = ALL_USERS[list(ALL_USERS)[-1]]["id"]+1
    existUserID.append(ID)
    return ID

def getSocketID(username:str) -> str:
    global socketIDs
    if username in socketIDs:
        return socketIDs[username]
    else:
        return ""

def getUserID(username:str) -> int:
    try:
        return ALL_USERS[username]["id"]
    except:
        return -1

def getUserStatus(username:str) -> int:
    try:
        return ALL_USERS[username]["status"]
    except:
        return -1

def addMessage(_time: str, username: str, message: str, userID: int, msg_ID: str,msg_type:str):
    global _HISTORY, _TIMELINE
    _HISTORY[msg_ID] = [_time, username, message, userID,msg_type]
    _TIMELINE.append(msg_ID)
    _TIMELINE = [_id for _id in _TIMELINE if _id in _HISTORY]
    while len(_HISTORY) > 500:
        del _HISTORY[_TIMELINE[0]]
    updateHistory()


def scanUsername(username:str) -> str:
    global ALL_USERS
    if ALL_USERS[username]["identify"] >= 7:
        return '<span style="color:rgb(255,215,0)">'+username+'</span>'
    else:
        return username
def userIdentify(event):
    global ALL_USERS
    return ALL_USERS[event["username"]]["identify"]

async def checkSocketID(ws,event):
    if getSocketID(event["username"]) != event["socketID"]:
        await ws.send(to_json({"action":"error","status":302}))#错误的连接ID
        # ALL_USERS[event["username"]]["ban_time"] = time()+3600
        return False
    return True

async def connect(ws, event):
    global ALL_USERS, USERS, USER_writing
    username: str = event["username"]
    new_ID = getUserID(username)
    if username in ALL_USERS:
        if event["type"] == "register":
            # username has already exist
            await ws.send(to_json({"action": "error", "status": 102}))
            return
        else:
            if ALL_USERS[username]["identify"] == -1:
                now_time = time()
                if ALL_USERS[username]["ban_time"] <= now_time:
                    ALL_USERS[username]["identify"] = 1
                    await connect(ws, event)
                    return
                else:
                    # user has been banned
                    await ws.send(to_json({"action": "error", "status": 111, "time": ALL_USERS[username]["ban_time"]-now_time}))
                    return
            elif ALL_USERS[username]["password"] != event["password"]:
                # wrong password
                await ws.send(to_json({"action": "error", "status": 103}))
                return
            else:
                # login ok
                if event["path"] == '/chat':
                    await ws.send(to_json({
                        "action": "confirm", 
                        "type": "connect", 
                        "status": 100, 
                        "animation": "true", 
                        "identify": userIdentify(event), 
                        "userID": getUserID(event["username"]), 
                        "user-status": ALL_USERS[event["username"]]["status"],
                        "socketID":createSocketID(event["username"])
                    }))
                else:
                    await ws.send(to_json({
                        "action": "confirm", 
                        "type": "connect", 
                        "status": 100, 
                        "identify": userIdentify(event), 
                        "userID": getUserID(event["username"]), 
                        "user-status": ALL_USERS[event["username"]]["status"],
                        "socketID":createSocketID(event["username"])
                    }))
    else:
        if event["type"] == "register":
            new_ID = createUserID()
            ALL_USERS[username] = {"password":event["password"],"identify": 1,"id": new_ID,"status": 120,"ban_time":0}
            while USER_writing:
                pass
            USER_writing = True
            with open('./users.json', 'w') as f:
                json.dump(ALL_USERS, f)
            USER_writing = False
            # register ok
            await ws.send(to_json({
                "action": "confirm", 
                "type": "connect", 
                "status": 105, 
                "userID": getUserID(event["username"]), 
                "user-status": ALL_USERS[event["username"]]["status"],
                "socketID":createSocketID(event["username"])
            }))
        else:
            # wrong name
            await ws.send(to_json({"action": "error", "status": 104}))
            return
    # await socketID_sender(ws,event)
    USERS[new_ID] = ws
    log.info(f"{username}进入了聊天室,在线人数: {str(len(USERS))}")


async def send_to_user(ws, event):
    global ALL_USERS, USERS
    message = event["message"][3:]
    cut = message.find(' ')
    send_to = message[:cut]
    message = message[cut+1:]
    _time = strftime('%H:%M:%S')
    if send_to in USERS and ALL_USERS[event["username"]]["status"] != 123:
        username = scanUsername(event["username"])
        log.info(f'发送给{send_to}: {_time} | {event["username"]}: {message}')
        await USERS[send_to].send(to_json({"action": "send", "username": event["username"], "message": [_time, '<i style="color:rgb(100,100,100);">来自'+username+'的私聊 </i>', message], "tag": "private", "userID": getUserID(event["username"]),"user-status":getUserStatus(event["username"])}))
        await ws.send(to_json({"action": "send", "username": event["username"], "message": [_time, '<i style="color:rgb(100,100,100);">发给'+send_to+'的私聊</i>', message], "tag": "private", "userID": getUserID(event["username"]),"user-status":getUserStatus(event["username"])}))
    elif send_to in ALL_USERS:
        # friend offline
        await ws.send(to_json({"action": "error", "status": 202}))
    else:
        # user does not exist
        await ws.send(to_json({"action": "error", "status": 201}))



async def sendall(ws, event):
    global USERS
    message = event["message"]
    _time = strftime('%H:%M:%S')
    username = scanUsername(event["username"])
    msg_ID = createMsgID()
    user_ID = getUserID(event["username"])
    addMessage(_time, username, message, getUserID(event["username"]), msg_ID,'history')
    log.message(f'发送给所有人: {_time} | {event["username"]}: {message}')
    await ws.send(to_json({
        "action": "send",
        "username": event["username"],
        "message": [_time, username, message],
        "tag": "public",
        "id": msg_ID,
        "userID": user_ID,
        "user-status":getUserStatus(event["username"]),
        "msg_type":'self'}))
    [await websocket.send(to_json({
        "action": "send",
        "username": event["username"],
        "message": [_time, username, message],
        "tag": "public",
        "id": msg_ID,
        "userID": user_ID,
        "user-status":getUserStatus(event["username"]),
        "msg_type":FRIEND_NET[str(receiver_ID)][user_ID] if user_ID in FRIEND_NET[str(receiver_ID)] else 'normal'}))
    for receiver_ID,websocket in USERS.items() if websocket != ws]


async def do_send(ws, event):
    event["message"] = event["message"].strip('\n')
    while True:
        if event["message"].count("\n\n") != 0:
            event["message"] = event["message"].replace('\n\n', '\n')
            continue
        else:
            break
    msg = event["message"]
    if msg.startswith("/m "):
        await send_to_user(ws, event)
    else:
        await sendall(ws, event)


async def getHistory(ws, event):
    msgs:list = []
    new_event = {"action": "addHistory", "messages": msgs}
    maxHistoryLength = 20
    timeline = TIMELINE()
    history = HISTORY()
    if event["id"] is None:
        while (len(msgs) < maxHistoryLength) and (len(timeline) != 0):
            msgID = timeline.pop()
            msgs.append(history[str(msgID)]+[msgID])
    else:
        eventID = event["id"]
        try:
            timeline = timeline[:timeline.index(eventID)]
        except:
            pass
        while (len(msgs) < maxHistoryLength) and (len(timeline) != 0):
            msgID = timeline.pop()
            msgs.append(history[msgID]+[msgID])
    await ws.send(to_json(new_event))


async def recallMsg(ws, event):
    global _HISTORY, USERS
    eventID = event["id"]
    send_event = to_json(
        {"action": "confirm", "type": "recallMsg", "id": eventID, "userID": getUserID(event["username"]), "recallUsername": event["username"]})
    del _HISTORY[eventID]
    del _TIMELINE[_TIMELINE.index(eventID)]
    updateHistory()
    [await webserver.send(send_event) for webserver in USERS.values()]


async def redirect(ws, event):
    global ALL_USERS, NET
    cuts = event["target"].split("?")
    target,search = (cuts[0],cuts[1]) if len(cuts) == 2 else (cuts[0],"")
    if target not in NET:
        await ws.send(to_json({"action": "confirm", "type": "redirect", "status": 300, "target": "/PageNotFound","search":search}))
        return
    if ALL_USERS[event["username"]]["identify"] < NET[target]:
        # redirect failed:permission denied
        await ws.send(to_json({"action": "error", "status": 301}))
    else:
        await ws.send(to_json({"action": "confirm", "type": "redirect", "status": 300, "target": target,"search":search}))


async def update_userInfo(ws, event):
    if event["type"] == "update-user-head-img":
        img = base64.b64decode(event["src"])
        with open(f'./Chat/static/images/head/{getUserID(event["username"])}.ico', 'wb') as f:
            f.write(img)
        await ws.send(to_json({"action": "update-profile", "type": "head-image", "userID": getUserID(event["username"])}))
        send_event = to_json({"action":"update-profile","type":"msg-user-profile-photo"})
        [await websocket.send(send_event) for websocket in USERS.values()]
    elif event["type"] == "update-user-status":
        global USER_writing
        ALL_USERS[event["username"]]["status"] = event["user-status"]
        while USER_writing:
            pass
        USER_writing = True
        with open("./users.json", 'w') as f:
            json.dump(ALL_USERS, f)
        USER_writing = False
        await ws.send(to_json({"action": "update-profile", "type": "user-status", "user-status": event["user-status"]}))
        send_event = to_json({"action":"update-profile","type":"msg-user-status","username":event["username"],"status":event["user-status"]})
        [await websocket.send(send_event) for websocket in USERS.values()]


async def addFriend(ws, event):
    global FRIEND_NET
    FRIEND_NET[str(getUserID(event["username"]))].append(
        getUserID(event["friend_name"]))
    # with open('')#TODO


async def getFriend(ws, event):
    global FRIEND_NET  # TODO

async def removeFriend(ws, event):
    global FRIEND_NET  # TODO


def disconnect(event):
    username = event["username"]
    try:
        del USERS[getUserID(username)]
        log.info(f"{username}退出了聊天室, 在线人数: {str(len(USERS))}")
    except:
        return

async def send_icon(ws,event):
    for icon_name in os.listdir('./static/images/status-icons'):
        with open(f"./static/images/status-icons/{icon_name}",'rb') as f:
            icon_data = str(base64.b64encode(f.read()),encoding='utf-8')
        await ws.send(to_json({"action":"confirm","type":"status-icon","name":icon_name,"data":icon_data})) 
    await ws.send(to_json({"action":"confirm","type":"status-icon","name":"end"}))      


async def ws_recv(ws, path):
    while True:
        try:
            event: dict[str, str] = json.loads(await ws.recv())
        except websockets.exceptions.ConnectionClosedOK: #type: ignore  
            continue
        log.info("接收: "+str(event))
        if event["action"] == "connect": 
            await connect(ws, event)
            continue
        elif not await checkSocketID(ws,event):
            await ws.send(to_json({'action': 'confirm', 'type': 'redirect', 'status': 300, 'target': '/chat', 'search': ''}))
        if event["action"] == "get-status-icons":
            await send_icon(ws,event)
        elif event["action"] == "send":
            await do_send(ws, event)
        elif event["action"] == "redirect":
            await redirect(ws, event)
        elif event["action"] == "getHistory":
            await getHistory(ws, event)
        elif event["action"] == "recallMsg":
            await recallMsg(ws, event)
        elif event["action"] == "update-userInfo":
            await update_userInfo(ws, event)
        elif event["action"] == "addFriend":
            await addFriend(ws,event)
        elif event["action"] == "getFriend":
            await getFriend(ws,event)
        elif event["action"] == "removeFriend":
            await removeFriend(ws,event)
        elif event["action"] == "disconnect":
            disconnect(event)
websocket_server = websockets.serve(ws_recv, '127.0.0.1', 5000, max_size=2**25) #type: ignore
print("ws server is running at 127.0.0.1:5000")
asyncio.get_event_loop().run_until_complete(websocket_server)
asyncio.get_event_loop().run_forever()